//! \file       backtacer.c
//! \brief Realisation of the backtracer functions.

#include <string.h>
#include <stdlib.h>

#include "backtracer.h"
#include "map_utils.h"
#include "memory_utils.h"
#include "log.h"

//------------------------------------------------------------------------------

TBGError DefaultBacktrace(func_map *pmap, void *frame_begin, void *curr_addr) {
    TBGError ret = 0;
    bool found = false;
    int i = 0;
    for(i = 0; i < pmap->size_; ++i) {
        unsigned int name_idx = pmap->map_[i].name_idx_;
        CHECK_DBG_ERR(name_idx < pmap->size_, BIG_INDEX);
        if(!strcmp(pmap->names_[name_idx], "main")) {
            found = true;
            break;
        }
    }
    CHECK_RET(found, NO_MAIN_FUNC, exit_point, ret); 
    
    const size_t tr_size = DEFAULT_TRACE_SIZE;
    trace_struct tr; //allocate!
    tr.trace_idx = calloc(tr_size, sizeof(int));
    tr.trace_addr = calloc(tr_size, sizeof(addr_t));
    CHECK_ERR(tr.trace_idx, CANNOT_ALLOC_MEMORY);
    CHECK_ERR(tr.trace_addr, CANNOT_ALLOC_MEMORY);

    CHECK_CALL(GenBacktraceFrames(pmap, frame_begin, curr_addr, 
                                  (void*) pmap->map_[i].addr_, &tr, tr_size),
               free_point, ret);
    PrintBacktrace(pmap, &tr);

free_point:
    free(tr.trace_idx);
    free(tr.trace_addr);
exit_point:
    return ret;
}

//------------------------------------------------------------------------------

void PrintBacktrace(func_map *pmap, trace_struct *stack_trace) {
    CHECK_DBG_ERR(stack_trace, NULL_PTR);
    CHECK_DBG_ERR(stack_trace->trace_addr, NULL_PTR);
    CHECK_DBG_ERR(stack_trace->trace_idx, NULL_PTR);
    CHECK_DBG_ERR(pmap, NULL_PTR);
    CHECK_DBG_ERR(pmap->map_, NULL_PTR);
    
    int i = 0;
    for(i = 0; i < stack_trace->trace_size; ++i) {
        int find_idx = stack_trace->trace_idx[i];
        CHECK_DBG_ERR(find_idx < (int) pmap->size_, BIG_INDEX);

        if(find_idx < 0)
            LogPrintf("[%x] Unknown call", stack_trace->trace_addr[i]);
        else {
            unsigned int name_idx = pmap->map_[find_idx].name_idx_;
            CHECK_DBG_ERR(name_idx < pmap->size_, BIG_INDEX);

            LogPrintf("[%x] %s + %x\n", stack_trace->trace_addr[i],
                      pmap->names_[name_idx],
                      stack_trace->trace_addr[i] - pmap->map_[find_idx].addr_);
        }
    }
    
}

//------------------------------------------------------------------------------

TBGError GenBacktraceFrames(func_map *pmap, void *frame_begin, void *curr_addr,
                            void *stop_addr, trace_struct *stack_trace,
                            size_t trace_max_size) {  
    TBGError ret = 0;
    CHECK_DBG_ERR(stack_trace, NULL_PTR);
    CHECK_DBG_ERR(stack_trace->trace_addr, NULL_PTR);
    CHECK_DBG_ERR(stack_trace->trace_idx, NULL_PTR);
    CHECK_DBG_ERR(pmap, NULL_PTR);
    CHECK_DBG_ERR(pmap->map_, NULL_PTR);

    size_t cur_paste_idx = 0;

    void *stack_addr = frame_begin;
    void *proc_addr = curr_addr;

//    PrintStack();

//    printf("\n\ninit stack_addr is [%x]\n", stack_addr);
//    printf("init proc_addr is [%x]\n\n", proc_addr);

    int find_res = _FindByAddr(pmap, (addr_t) proc_addr);


    while(cur_paste_idx < trace_max_size) { 

        stack_trace->trace_idx[cur_paste_idx] = find_res;
        stack_trace->trace_addr[cur_paste_idx++] = (addr_t) proc_addr;

        if(find_res < 0)
            break;
        CHECK_DBG_ERR(find_res < pmap->size_, BIG_INDEX);
        if(pmap->map_[find_res].addr_ == (addr_t) stop_addr)
            break;

//        printf("stack_addr is [%x]\n", stack_addr);
//        printf("proc_addr is [%x]\n\n", proc_addr);

        CHECK_RET(stack_addr, INVALID_STACK_TRACE, exit_point, ret);
        CHECK_CALL(ReadData((addr_t)stack_addr, (void*) &proc_addr, 1),
                   exit_point, ret);
        CHECK_CALL(ReadData((addr_t)((char*)stack_addr - 4),
                   (void*) &stack_addr, 1), exit_point, ret);

//        proc_addr  = *((void**)stack_addr);
//        stack_addr = *((void**)(stack_addr - 4));

        find_res = _FindByAddr(pmap, (addr_t) proc_addr);
    }

    stack_trace->trace_size = cur_paste_idx;
exit_point:
    return ret;
}

